home *** CD-ROM | disk | FTP | other *** search
/ Celestin Apprentice 2 / Apprentice-Release2.iso / Source Code / C / Applications / Newswatcher 2.0b22 / NW Source / Source / nntp.c < prev    next >
Encoding:
Text File  |  1994-11-22  |  50.5 KB  |  1,844 lines  |  [TEXT/MMCC]

  1. /*----------------------------------------------------------------------------
  2.  
  3.     nntp.c
  4.  
  5.     This reusable module implements an interface to NNTP (USENET news) servers.
  6.     
  7.     The following functions are exported:
  8.     
  9.         NntpOpen - Open an NNTP stream.
  10.         NntpClose - Close an NNTP stream.
  11.         NntpAbort - Abort an NNTP stream.
  12.         NntpIdle - Perform idle time tasks.
  13.         NntpSetStreamOptions - Set stream options.
  14.         NntpGetGroupList - Get full group list or new groups list.
  15.         NntpGetGroupNames - Get group names.
  16.         NntpGetGroupInfo - Get info for a single group.
  17.         NntpGetMultipleGroupInfo - Get info for multiple groups.
  18.         NntpGetHeaders - Get selected article headers.
  19.         NntpGetArticle - Get an article.
  20.         NntpGetChunkyArticle - Get an article in chunks.
  21.         NntpPostArticle - Post an article.
  22.         NntpAuthorize - Authorize user.
  23.         NntpGetHello - Get server hello message.
  24.         NntpGetHelp - Get server help text.
  25.         NntpGetIPAddr - Get server IP address.
  26.         
  27.     The following functions are used to get error information:
  28.         
  29.         NntpGetServerErrInfo - Get server error information.
  30.         NntpGetOSErr - Get OS error number.
  31.         
  32.     The following utility functions are also exported:
  33.     
  34.         IsLegalBinHexLine - Check line for BinHex encoded text.
  35.         IsLegalUULine - Check line for uuencode text.
  36.         
  37.     The NetInit function in module net.c must be called before calling any of
  38.     the functions in this module. You also must call both the NntpIdle and the 
  39.     NetIdle functions in your idle loop, and the NetTerm function at program termination.
  40.         
  41.     A "stream" is an abstraction representing a bidirectional network connection
  42.     to an NNTP server. A stream is represented as a handle of type "NntpStreamHandle". 
  43.     These stream handles are opaque. You may copy them and pass them as parameters 
  44.     to functions in nntp.c, but you are prohibited from accessing the contents of
  45.     the memory blocks referenced by the stream handles. Only the functions in 
  46.     nntp.c are permitted to manipulate the contents of these blocks.
  47.     
  48.     The functions return a value of type NntpErr as the function result:
  49.     
  50.         nntpNoErr                no error occurred
  51.         nntpNoSuchGroupErr        no such group or group access restriction
  52.         nntpNoSuchArticleErr    no such article
  53.         nntpServerErr            server error
  54.         nntpOSErr                OS error
  55.         
  56.     If the function result is nntpServerErr, the NntpGetServerErrInfo function should
  57.     be called to get information about the server error. On server errors, no such group
  58.     errors, and no such article errors, the stream is still open on return to the caller
  59.     (except in NntpOpen, which creates the stream only if no errors of any kind occur).
  60.     
  61.     If the function result is nntpOSErr, the NntpGetOSErr function should be called
  62.     to get the OS error number:
  63.  
  64.         userCanceledErr        if the user canceled the operation
  65.         other                any other OS, Toolbox, or MacTCP error code
  66.         
  67.     On an OS error (which includes userCanceledErr), the connection to the
  68.     news server may be aborted, but the NNTP stream is left allocated. The module will
  69.     automatically attempt to establish a new connection the next time the stream is
  70.     used.
  71.     
  72.     All group names, header names, message id, and pattern strings passed as parameters
  73.     to functions in this module are restricted to a maximum of 127 characters. If they 
  74.     exceed this maximum, they are truncated.
  75.     
  76.     The following options are associated with each stream:
  77.     
  78.         idleTime        stream is automatically closed if idle for this 
  79.                         many minutes, or 0 for no automatic close
  80.         useXPAT            true to use XPAT command for searches
  81.         sendModeReader    true to send MODE READER command after connect
  82.         batchedCmds        true to use batched GROUP commands
  83.         newConnection    true to establish new connection before getting
  84.                         group info
  85.         
  86.     The default values are idleTime=0, useXPAT=false, sendModeReader=true, 
  87.     batchedCmds=false, and newConnection=true. These options should work with
  88.     any properly functioning NNTP server. The four Boolean options can be changed
  89.     to improve performance, but they don't work with all servers. See the individual
  90.     function descriptions more for more details.
  91.     
  92.     Copyright © 1994, Northwestern University.
  93.  
  94. ----------------------------------------------------------------------------*/
  95.  
  96. #include <stdlib.h>
  97. #include <string.h>
  98. #include <stdio.h>
  99. #include <ctype.h>
  100.  
  101. #include "def.h"
  102. #include "nntp.h"
  103. #include "net.h"
  104. #include "strutil.h"
  105. #include "qsort.h"
  106. #include "memutil.h"
  107.  
  108.  
  109.  
  110. /* Types. */
  111.  
  112. typedef struct TStream {
  113.     NetStreamHandle netStream;            /* net stream handle, or nil if closed */
  114.     NntpStreamOptions options;            /* stream options */
  115.     Boolean serverHasXPAT;                /* true if server supports XPAT command */
  116.     unsigned long addr;                    /* server IP address */
  117.     unsigned short port;                /* server port number */
  118.     CStr255 helloMsg;                    /* hello message */
  119.     char curGroup[128];                    /* current group on server */
  120.     unsigned long lastTransactionTime;    /* time of last transaction on stream */
  121.     struct TStream **next;                /* handle to next open NNTP stream */
  122. } TStream, *TStreamPtr, **TStreamHandle;
  123.     
  124.  
  125.  
  126. /*    Global variables. */
  127.  
  128. static OSErr gErr = noErr;                /* OS error code */
  129. static CStr255 gCommand = "";            /* server command */
  130. static long gResponseCode = 0;            /* server response code */
  131. static CStr255 gResponse = "";            /* server response message */
  132. static TStreamHandle gStreamList = nil;    /* handle to list of open NNTP streams */
  133. static Boolean gRequireEncodedTextBeginLine;    /* true if attached file must
  134.                                                    include "begin" flag line */
  135.  
  136. /*    Globals used by DoOneGroupCmdResponse and NntpGetMultipleGroupInfo. */
  137.  
  138. static NntpGroupInfoHandle gInfo;        /* handle to group info array */
  139. static long gInfoIndex;                    /* current index in group info array */
  140. static Boolean gGroupInfoServerError;    /* true if server error encountered */
  141.  
  142.  
  143.  
  144. /*----------------------------------------------------------------------------
  145.     SetLastTransactionTime 
  146.     
  147.     Record the transaction time on an NNTP stream.
  148.     
  149.     Entry:    s = handle to stream.
  150. ----------------------------------------------------------------------------*/
  151.  
  152. static void SetLastTransactionTime (TStreamHandle s)
  153. {
  154.     unsigned long lastTransactionTime;
  155.     
  156.     GetDateTime(&lastTransactionTime);
  157.     (**s).lastTransactionTime = lastTransactionTime;
  158. }
  159.  
  160.  
  161.  
  162. /*----------------------------------------------------------------------------
  163.     SetCurrentGroup 
  164.     
  165.     Record the current group on an NNTP stream.
  166.     
  167.     Entry:    s = handle to stream.
  168.             group = group name.
  169. ----------------------------------------------------------------------------*/
  170.  
  171. static void SetCurrentGroup (TStreamHandle s, char *group)
  172. {
  173.     short len;
  174.     
  175.     len = strlen(group);
  176.     if (len > 127) len = 127;
  177.     BlockMoveData(group, (**s).curGroup, len);
  178.     *((**s).curGroup + len) = 0;
  179. }
  180.  
  181.  
  182.  
  183. /*----------------------------------------------------------------------------
  184.     Authorize 
  185.     
  186.     Authorize user.
  187.     
  188.     Entry:    netStream = pointer to net stream.
  189.             username = username.
  190.             password = password.
  191.             
  192.     Exit:    function result = result code.
  193. ----------------------------------------------------------------------------*/
  194.  
  195. static NntpErr Authorize (NetStreamHandle netStream, char *username, char *password)
  196. {
  197.     if (*username == 0 || *password == 0) return nntpNoErr;
  198.  
  199.     sprintf(gCommand, "AUTHINFO USER %s", username);
  200.     gErr = NetCommand(netStream, gCommand, &gResponseCode, gResponse);
  201.     if (gErr != noErr) goto exit1;
  202.     if (gResponseCode != 381) goto exit2;
  203.     
  204.     sprintf(gCommand, "AUTHINFO PASS %s", password);
  205.     gErr = NetCommand(netStream, gCommand, &gResponseCode, gResponse);
  206.     if (gErr != noErr) goto exit1;
  207.     if (gResponseCode != 281) goto exit2;
  208.     
  209.     return nntpNoErr;
  210.     
  211. exit1:
  212.  
  213.     return nntpOSErr;
  214.     
  215. exit2:
  216.  
  217.     NetClose(netStream);
  218.     return nntpServerErr;
  219. }
  220.  
  221.  
  222.  
  223. /*----------------------------------------------------------------------------
  224.     ReOpenConnection 
  225.     
  226.     Reopen a connection to an NNTP server.
  227.     
  228.     Entry:    s = handle to stream.
  229.     
  230.     Exit:    function result = result code.
  231. ----------------------------------------------------------------------------*/
  232.  
  233. static NntpErr ReOpenConnection (TStreamHandle s)
  234. {
  235.     NetStreamHandle netStream;
  236.     char username[32];
  237.     char password[32];
  238.     NntpErr nntpErr;
  239.     
  240.     if ((**s).netStream != nil) return nntpNoErr;
  241.     
  242.     NetIdle();
  243.     
  244.     gErr = NetOpen((**s).addr, (**s).port, &netStream, &gResponseCode, gResponse);
  245.     if (gErr != noErr) return nntpOSErr;
  246.     if (gResponseCode != 200 && gResponseCode != 201) return nntpServerErr;
  247.     strcpy((**s).helloMsg, gResponse);
  248.  
  249.     if ((**s).options.sendModeReader) {
  250.         strcpy(gCommand, "MODE READER");
  251.         gErr = NetCommand(netStream, gCommand, &gResponseCode, gResponse);
  252.         if (gErr != noErr) return nntpOSErr;
  253.     }
  254.     
  255.     if ((**s).options.authOnConnect) {
  256.         strcpy(username, (**s).options.username);
  257.         strcpy(password, (**s).options.password);
  258.         nntpErr = Authorize(netStream, username, password);
  259.         if (nntpErr != nntpNoErr) goto exit;
  260.     }
  261.     
  262.     (**s).netStream = netStream;
  263.     *(**s).curGroup = 0;
  264.     SetLastTransactionTime(s);
  265.  
  266.     return nntpNoErr;
  267.     
  268. exit:
  269.  
  270.     (**s).netStream = nil;
  271.     return nntpErr;
  272. }
  273.  
  274.  
  275. /*----------------------------------------------------------------------------
  276.     DoOneGroupCmdResponse 
  277.     
  278.     Process one GROUP command response for batched GROUP commands.
  279.     
  280.     Entry:    responseCode = resonse code.
  281.             response = response string.
  282.             
  283.     This function is used by NntpGetMultipleGroupInfo.
  284. ----------------------------------------------------------------------------*/
  285.  
  286. static void DoOneGroupCmdResponse (long responseCode, CStr255 response)
  287. {
  288.     NntpGroupInfoPtr x;
  289.     char *p;
  290.     char state;
  291.  
  292.     if (gGroupInfoServerError) return;
  293.     state = MyHGetState(gInfo);
  294.     MyHLock(gInfo);
  295.     x = *gInfo + gInfoIndex;
  296.     if (responseCode == 211) {
  297.         p = response;
  298.         CrackNum(&p);
  299.         x->count = CrackNum(&p);
  300.         x->first = CrackNum(&p);
  301.         x->last = CrackNum(&p);
  302.         x->ok = true;
  303.     } else if (responseCode == 502) {
  304.         x->count = 0;
  305.         x->first = 1;
  306.         x->last = 0;
  307.         x->ok = true;
  308.     } else if (responseCode == 411) {
  309.         x->ok = false;
  310.     } else {
  311.         gGroupInfoServerError = true;
  312.     }
  313.     gInfoIndex++;
  314.     MyHSetState(gInfo, state);
  315. }
  316.  
  317.  
  318.  
  319. /*----------------------------------------------------------------------------
  320.     SortHeadersCompare 
  321.     
  322.     This comparison function used to sort a header info array into increasing 
  323.     order by article number.
  324.     
  325.     Entry:    p = pointer to first NntpHeaderInfo struct.
  326.             q = pointer to second NntpHeaderInfo struct.
  327.             
  328.     Exit:    function result =
  329.                 -1 if p->number < q->number
  330.                  0 if p->number == q->number
  331.                  1 if p->number > q->number
  332. ----------------------------------------------------------------------------*/
  333.  
  334. static short SortHeadersCompare (NntpHeaderInfoPtr p, NntpHeaderInfoPtr q)
  335. {
  336.     static short counter = 0;
  337.  
  338.     if((++counter & 0x1f) == 0) {
  339.         if (NetGiveTime(false) == userCanceledErr) CancelFastQSort();
  340.         counter = 0;
  341.     }
  342.     
  343.     if (p->number < q->number) {
  344.         return -1;
  345.     } else if (p->number == q->number) {
  346.         return 0;
  347.     } else {
  348.         return 1;
  349.     }
  350. }
  351.  
  352.  
  353.  
  354. /*----------------------------------------------------------------------------
  355.     IsLegalBinHexLine 
  356.     
  357.     Check a line to see if it contains legal BinHex encoded text.
  358.     
  359.     Entry:    p = pointer to line.
  360.             len = length of line.
  361.             
  362.     Exit:    function result = true if legal BinHex.
  363. ----------------------------------------------------------------------------*/
  364.  
  365. Boolean IsLegalBinHexLine (unsigned char *p, long len)
  366. {
  367.     unsigned char *pEnd, *pBegin;
  368.     unsigned char c;
  369.     
  370.     static Boolean legalBinHexChar[] = {
  371.         /* 0x00 */
  372.             false,    false,    false,    false,    false,    false,    false,    false,
  373.             false,    false,    false,    false,    false,    false,    false,    false,
  374.         /* 0x10 */
  375.             false,    false,    false,    false,    false,    false,    false,    false,
  376.             false,    false,    false,    false,    false,    false,    false,    false,
  377.         /* 0x20 */
  378.             false,    true,    true,    true,    true,    true,    true,    true,
  379.             true,    true,    true,    true,    true,    true,    false,    false,
  380.         /* 0x30 */
  381.             true,    true,    true,    true,    true,    true,    true,    false,
  382.             true,    true,    true,    false,    false,    false,    false,    false,
  383.         /* 0x40 */
  384.             true,    true,    true,    true,    true,    true,    true,    true,
  385.             true,    true,    true,    true,    true,    true,    true,    false,
  386.         /* 0x50 */
  387.             true,    true,    true,    true,    true,    true,    true,    false,
  388.             true,    true,    true,    true,    false,    false,    false,    false,
  389.         /* 0x60 */
  390.             true,    true,    true,    true,    true,    true,    true,    false,
  391.             true,    true,    true,    true,    true,    true,    false,    false,
  392.         /* 0x70 */
  393.             true,    true,    true,    false,    false,    false,    false,    false,
  394.             false,    false,    false,    false,    false,    false,    false,    false,
  395.         /* 0x80 */
  396.             false,    false,    false,    false,    false,    false,    false,    false,
  397.             false,    false,    false,    false,    false,    false,    false,    false,
  398.             false,    false,    false,    false,    false,    false,    false,    false,
  399.             false,    false,    false,    false,    false,    false,    false,    false,
  400.             false,    false,    false,    false,    false,    false,    false,    false,
  401.             false,    false,    false,    false,    false,    false,    false,    false,
  402.             false,    false,    false,    false,    false,    false,    false,    false,
  403.             false,    false,    false,    false,    false,    false,    false,    false,
  404.             false,    false,    false,    false,    false,    false,    false,    false,
  405.             false,    false,    false,    false,    false,    false,    false,    false,
  406.             false,    false,    false,    false,    false,    false,    false,    false,
  407.             false,    false,    false,    false,    false,    false,    false,    false,
  408.             false,    false,    false,    false,    false,    false,    false,    false,
  409.             false,    false,    false,    false,    false,    false,    false,    false,
  410.             false,    false,    false,    false,    false,    false,    false,    false,
  411.             false,    false,    false,    false,    false,    false,    false,    false,
  412.         };
  413.  
  414.     for (pBegin = p, pEnd = p + len; p < pEnd; p++)
  415.         if (!legalBinHexChar[*p]) return false;
  416.     c = *pBegin;
  417.     for (p = pBegin+1; p < pEnd; p++)
  418.         if (*p != c) return true;
  419.     return false;
  420. }
  421.  
  422.  
  423.  
  424. /*----------------------------------------------------------------------------
  425.     IsLegalUULine 
  426.     
  427.     Check a line to see if it contains legal uuencode text.
  428.     
  429.     Entry:    p = pointer to line.
  430.             len = length of line.
  431.             
  432.     Exit:    function result = true if legal uuencode.
  433. ----------------------------------------------------------------------------*/
  434.  
  435. Boolean IsLegalUULine (unsigned char *p, long len)
  436. {
  437.     long n;
  438.     unsigned char *pEnd, *pBegin;
  439.     unsigned char c;
  440.     
  441.     n = *p - ' ';
  442.     if (n < 0 || n > 63) return false;
  443.     n = (n+2)/3*4;
  444.     if (len > n+3 || len < n-3) return false;
  445.     for (pBegin = p, pEnd = p + len, p++; p < pEnd; p++)
  446.         if (*p < ' ' || *p > '`') return false;
  447.     c = *pBegin;
  448.     for (p = pBegin+1; p < pEnd; p++)
  449.         if (*p != c) return true;
  450.     return false;
  451. }
  452.  
  453.  
  454.  
  455. /*----------------------------------------------------------------------------
  456.     CheckForAttachedFile 
  457.     
  458.     Check for attached file and truncate article if BinHex or uuencode
  459.     text encountered.
  460.     
  461.     Entry:    t = handle to raw text received from server so far.
  462.             tLen = length of text received from server so far.
  463.             *position = saved position in text (0 on first call).
  464.             gRequireEncodedTextBeginLine = true if the encoded text
  465.                 must include a begin "flag" line.
  466.             
  467.     Exit:    function result = true if text should be truncated.
  468.             *position = updated saved position in text if no truncation,
  469.                 length of truncated text if truncated.
  470.                 
  471.     This function looks for the flag line (if required), followed by 
  472.     two adjacent lines of the same length at least 60 characters long,
  473.     each of which is either a legal BinHex line or a legal uuencode line.
  474. ----------------------------------------------------------------------------*/
  475.  
  476. static Boolean CheckForAttachedFile (Handle t, long tLen, long *position)
  477. {
  478.     long lenA, lenB;
  479.     unsigned char *p, *pEnd, *q, *r;
  480.     unsigned char *flagLine, *lineA, *lineB, *nextLine;
  481.     Boolean haveBeginFlagLine, beginFlagLineIsBinHex, foundIt;
  482.  
  483.     p = (unsigned char*)(*t + *position);
  484.     pEnd = (unsigned char*)(*t + tLen - 1);
  485.     
  486.     while (p < pEnd) {
  487.         if (gRequireEncodedTextBeginLine) {
  488.             haveBeginFlagLine = false;
  489.             flagLine = p;
  490.             while (flagLine < pEnd) {
  491.                 q = flagLine;
  492.                 while (q < pEnd && *q != CR) q++;
  493.                 while (q < pEnd && (*q == CR || *q == LF)) q++;
  494.                 if (q >= pEnd) break;
  495.                 if (*flagLine == '(') {
  496.                     if (flagLine + 39 >= pEnd) break;
  497.                     if (strncmp((char*)flagLine, 
  498.                         "(This file must be converted with BinHex", 39) == 0) 
  499.                     {
  500.                         haveBeginFlagLine = true;
  501.                         beginFlagLineIsBinHex = true;
  502.                         break;
  503.                     }
  504.                 } else if (*flagLine == 'b') {
  505.                     if (flagLine + 9 >= pEnd) break;
  506.                     if (strncmp((char*)flagLine, "begin ", 6) == 0) {
  507.                         r = flagLine + 6;
  508.                         if (isoctal(*r) && isoctal(*(r+1)) && isoctal(*(r+2))) {
  509.                             haveBeginFlagLine = true;
  510.                             beginFlagLineIsBinHex = false;
  511.                             break;
  512.                         }
  513.                     }
  514.                 } 
  515.                 flagLine = q;
  516.             }
  517.             if (!haveBeginFlagLine) {
  518.                 *position = (char*)flagLine - *t;
  519.                 return false;
  520.             }
  521.             p = flagLine;
  522.             lineA = q;
  523.             if (lineA >= pEnd) break;
  524.         } else {
  525.             lineA = p;
  526.         }
  527.         q = lineA;
  528.         while (q < pEnd && *q != CR) q++;
  529.         lenA = q - lineA;
  530.         while (q < pEnd && (*q == CR || *q == LF)) q++;
  531.         lineB = q;
  532.         if (lineB >= pEnd) break;
  533.         nextLine = gRequireEncodedTextBeginLine ? lineA : lineB;
  534.         if (lenA < 60) {
  535.             p = nextLine;
  536.             continue;
  537.         }
  538.         q = lineB;
  539.         while (q < pEnd && *q != CR) q++;
  540.         if (q >= pEnd) break;
  541.         lenB = q - lineB;
  542.         if (lenA != lenB) {
  543.             p = nextLine;
  544.             continue;
  545.         }
  546.         if (gRequireEncodedTextBeginLine) {
  547.             if (beginFlagLineIsBinHex) {
  548.                 foundIt = IsLegalBinHexLine(lineA, lenA) && IsLegalBinHexLine(lineB, lenB);
  549.             } else {
  550.                 foundIt = IsLegalUULine(lineA, lenA) && IsLegalUULine(lineB, lenB);
  551.             }
  552.         } else {
  553.             foundIt = (IsLegalBinHexLine(lineA, lenA) && IsLegalBinHexLine(lineB, lenB)) ||
  554.                 (IsLegalUULine(lineA, lenA) && IsLegalUULine(lineB, lenB));
  555.         }
  556.         if (foundIt) {
  557.             *position = (char*)lineA - *t;
  558.             return true;
  559.         } else {
  560.             p = nextLine;
  561.             continue;
  562.         }
  563.     }
  564.     
  565.     *position = (char*)p - *t;
  566.     return false;
  567. }
  568.  
  569.  
  570.  
  571. /*----------------------------------------------------------------------------
  572.     NntpOpen 
  573.     
  574.     Open an NNTP stream.
  575.     
  576.     Entry:    host = server host address (domain name or dotted 
  577.                 decimal IP address).
  578.             options = pointer to server options, or nil to set default options.
  579.     
  580.     Exit:    function result = result code.
  581.             stream = handle to opened stream, 
  582.                 or nil if function result != nntpNoErr.
  583.             
  584.     If the sendModeReader stream option is set, a "MODE READER" command is
  585.     sent to the server after connecting. This command is also sent in the
  586.     future on any automatic reconnects to the server. Some INN servers require
  587.     this command.
  588. ----------------------------------------------------------------------------*/
  589.  
  590. NntpErr NntpOpen (char *host, NntpStreamOptions *options, NntpStreamHandle *stream)
  591. {
  592.     TStreamHandle s;
  593.     unsigned long addr;
  594.     unsigned short port;
  595.     NetStreamHandle netStream;
  596.     CStr255 helloMsg;
  597.     NntpErr nntpErr;
  598.     
  599.     *stream = nil;
  600.     *gCommand = 0;
  601.     
  602.     gErr = NetNameToAddr(host, kNNTPPort, &addr, &port);
  603.     if (gErr != noErr) return nntpOSErr;
  604.     
  605.     gErr = NetOpen(addr, port, &netStream, &gResponseCode, gResponse);
  606.     if (gErr != noErr) return nntpOSErr;
  607.     if (gResponseCode != 200 && gResponseCode != 201) goto exit1;
  608.     strcpy(helloMsg, gResponse);
  609.     
  610.     if (options == nil || options->sendModeReader) {
  611.         strcpy(gCommand, "MODE READER");
  612.         gErr = NetCommand(netStream, gCommand, &gResponseCode, gResponse);
  613.         if (gErr != noErr) return nntpOSErr;
  614.     }
  615.     
  616.     if (options != nil && options->authOnConnect) {
  617.         nntpErr = Authorize(netStream, options->username, options->password);
  618.         if (nntpErr != nntpNoErr) return nntpErr;
  619.     }
  620.     
  621.     gErr = MyNewHandle(sizeof(TStream), &s);
  622.     if (gErr != noErr) goto exit2;
  623.     
  624.     (**s).netStream = netStream;
  625.     if (options == nil) {
  626.         (**s).options.idleTime = 0;
  627.         (**s).options.useXPAT = false;
  628.         (**s).options.sendModeReader = true;
  629.         (**s).options.batchedCmds = false;
  630.         (**s).options.newConnection = true;
  631.         (**s).options.authOnConnect = false;
  632.     } else {
  633.         (**s).options = *options;
  634.     }
  635.     (**s).serverHasXPAT = true;
  636.     (**s).addr = addr;
  637.     (**s).port = port;
  638.     strcpy((**s).helloMsg, helloMsg);
  639.     *(**s).curGroup = 0;
  640.     SetLastTransactionTime(s);
  641.     (**s).next = gStreamList;
  642.     gStreamList = s;
  643.  
  644.     *stream = (NntpStreamHandle)s;
  645.     return nntpNoErr;
  646.     
  647. exit1:
  648.  
  649.     NetClose(netStream);
  650.     return nntpServerErr;
  651.     
  652. exit2:
  653.  
  654.     NetClose(netStream);
  655.     return nntpOSErr;
  656.     
  657. }
  658.  
  659.  
  660.  
  661. /*----------------------------------------------------------------------------
  662.     NntpClose 
  663.     
  664.     Close an NNTP stream.
  665.     
  666.     Entry:    stream = handle to stream.
  667.     
  668.     Exit:    function result = result code (always nntpNoErr).
  669. ----------------------------------------------------------------------------*/
  670.  
  671. NntpErr NntpClose (NntpStreamHandle stream)
  672. {
  673.     TStreamHandle s, prev, cur;
  674.     NetStreamHandle netStream;
  675.     
  676.     s = (TStreamHandle)stream;
  677.     
  678.     for (prev = nil, cur = gStreamList; 
  679.         cur != nil && cur != s; 
  680.         prev = cur, cur = (**cur).next) /* do nothing */ ;
  681.     if (cur != nil) {
  682.         if (prev == nil) {
  683.             gStreamList = (**cur).next;
  684.         } else {
  685.             (**prev).next = (**cur).next;
  686.         }
  687.     }
  688.     
  689.     netStream = (**s).netStream;
  690.     if (netStream != nil) NetClose(netStream);
  691.     MyDisposeHandle(s);
  692.     return nntpNoErr;
  693. }
  694.  
  695.  
  696.  
  697. /*----------------------------------------------------------------------------
  698.     NntpAbort 
  699.     
  700.     Abort an NNTP stream.
  701.     
  702.     Entry:    stream = handle to stream.
  703.     
  704.     Exit:    function result = result code (always nntpNoErr).
  705. ----------------------------------------------------------------------------*/
  706.  
  707. NntpErr NntpAbort (NntpStreamHandle stream)
  708. {
  709.     TStreamHandle s;
  710.     NetStreamHandle netStream;
  711.     
  712.     s = (TStreamHandle)stream;
  713.     netStream = (**s).netStream;
  714.     if (netStream == nil) return nntpNoErr;
  715.     NetClose(netStream);
  716.     (**s).netStream = nil;
  717.     return nntpNoErr;
  718. }
  719.  
  720.  
  721.  
  722. /*----------------------------------------------------------------------------
  723.     NntpIdle 
  724.     
  725.     Handle idle time tasks.
  726.     
  727.     Exit:    function result = result code (always nntpNoErr).
  728.     
  729.     This function checks all the open NNTP streams to see if their idle
  730.     timers have expired. Any streams with expired timers are closed.
  731. ----------------------------------------------------------------------------*/
  732.  
  733. NntpErr NntpIdle (void)
  734. {
  735.     TStreamHandle s;
  736.     NetStreamHandle netStream;
  737.     short idleTime;
  738.     unsigned long lastTransactionTime, curTime;
  739.     
  740.     GetDateTime(&curTime);
  741.     for (s = gStreamList; s != nil; s = (**s).next) {
  742.         netStream = (**s).netStream;
  743.         idleTime = (**s).options.idleTime;
  744.         if (netStream != nil && idleTime != 0) {
  745.             lastTransactionTime = (**s).lastTransactionTime;
  746.             if (curTime > lastTransactionTime + 60*idleTime) {
  747.                 NetClose(netStream);
  748.                 (**s).netStream = nil;
  749.             }
  750.         }
  751.     }
  752.     return nntpNoErr;
  753. }
  754.  
  755.  
  756.  
  757. /*----------------------------------------------------------------------------
  758.     NntpSetStreamOptions
  759.     
  760.     Set stream options.
  761.     
  762.     Entry:    stream = handle to stream.
  763.             options = pointer to stream options.
  764.     
  765.     Exit:    function result = result code (always nntpNoErr).
  766. ----------------------------------------------------------------------------*/
  767.  
  768. NntpErr NntpSetStreamOptions (NntpStreamHandle stream, NntpStreamOptions *options)
  769. {
  770.     TStreamHandle s;
  771.     
  772.     s = (TStreamHandle)stream;
  773.     (**s).options = *options;
  774.     return nntpNoErr;
  775. }
  776.  
  777.  
  778.  
  779. /*----------------------------------------------------------------------------
  780.     NntpGetGroupList 
  781.     
  782.     Get the full group list or a new groups list from the server.
  783.     
  784.     Entry:    stream = handle to stream.
  785.             time = 0 to get full group list.
  786.             time != 0 to only get new groups created since the specified time.
  787.             needNumbers = true to request first, last, and count fields.
  788.     
  789.     Exit:    function result = result code.
  790.             info = handle to array of group info.
  791.             strings = handle to group name strings.
  792.             numGroups = number of groups.
  793.             
  794.     The time parameter is in GetDateTime format (seconds since 01/01/04). It
  795.     is interpreted by the server, not by the Mac. To avoid missing new groups
  796.     due to differences between the server's time and the Mac's time, you
  797.     should subtract a healthy amount from the time of your last new groups 
  798.     check (e.g., 36 hours), then discard any duplicate groups returned.  
  799.             
  800.     The strings block contains the C-format group names. Group names are 
  801.     truncated to 127 characters.
  802.     
  803.     The first, last, and status fields are read from the server.
  804.     The count fields are computed as max(0, last - first + 1).
  805.     The ok fields are set to true.
  806.     
  807.     If you don't need the first, last, and count fields, pass 
  808.     needNumbers=false. This speeds up the function quite a bit, especially
  809.     when getting a large full group list.
  810. ----------------------------------------------------------------------------*/
  811.  
  812. NntpErr NntpGetGroupList (NntpStreamHandle stream, unsigned long time, 
  813.     Boolean needNumbers, NntpGroupInfoHandle *info, Handle *strings, long *numGroups)
  814. {
  815.     TStreamHandle s;
  816.     NetStreamHandle netStream;
  817.     DateTimeRec timeRec;
  818.     Handle text=nil;
  819.     NntpGroupInfoHandle theInfo=nil;
  820.     char *p, *pEnd, *q, *r;
  821.     NntpGroupInfoPtr x, xEnd;
  822.     long n, len;
  823.     NntpErr nntpErr;
  824.     
  825.     s = (TStreamHandle)stream;
  826.     nntpErr = ReOpenConnection(s);
  827.     if (nntpErr != nntpNoErr) return nntpErr;
  828.     netStream = (**s).netStream;
  829.     
  830.     if (time == 0) {
  831.         strcpy(gCommand, "LIST");
  832.     } else {
  833.         SecondsToDate(time, &timeRec);
  834.         timeRec.year = timeRec.year % 100;
  835.         sprintf(gCommand, "NEWGROUPS %02d%02d%02d %02d%02d%02d", 
  836.             timeRec.year, timeRec.month, timeRec.day,
  837.             timeRec.hour, timeRec.minute, timeRec.second);
  838.     }
  839.     
  840.     gErr = NetCommand(netStream, gCommand, &gResponseCode, gResponse);
  841.     if (gErr != noErr) goto exit1;
  842.     if (gResponseCode != (time == 0 ? 215 : 231)) goto exit2;
  843.     
  844.     gErr = NetGetText(netStream, &text);
  845.     if (gErr != noErr) goto exit1;
  846.  
  847.     pEnd = *text + MyGetHandleSize(text);
  848.     n = 0;
  849.     for (p = *text; p < pEnd; p++) if (*p == CR) n++;
  850.  
  851.     gErr = MyNewHandle(n*sizeof(NntpGroupInfo), &theInfo);
  852.     if (gErr != noErr) goto exit3;
  853.  
  854.     MyHLock(text);
  855.     MyHLock(theInfo);
  856.     p = q = *text;
  857.     for (x = *theInfo, xEnd = x + n; x < xEnd; x++) {
  858.         gErr = NetGiveTime(false);
  859.         if (gErr != noErr) goto exit3;
  860.         r = p;
  861.         while (*r != ' ' && *r != CR) r++;
  862.         len = r-p;
  863.         if (len > 127) len = 127;
  864.         x->offset = q - *text;
  865.         if (needNumbers) {
  866.             x->last = CrackNum(&r);
  867.             x->first = CrackNum(&r);
  868.             x->count = x->last - x->first + 1;
  869.             if (x->count < 0) x->count = 0;
  870.         } else {
  871.             while (*r == ' ') r++;
  872.             while (*r != ' ' && *r != CR) r++;
  873.             while (*r == ' ') r++;
  874.             while (*r != ' ' && *r != CR) r++;
  875.         }
  876.         while (*r == ' ') r++;
  877.         x->status = tolower(*r);
  878.         while (*r != CR) r++;
  879.         x->ok = true;
  880.         *(p+len) = 0;
  881.         strcpy(q, p);
  882.         q += len+1;
  883.         p = r+1;
  884.     }
  885.     len = q-*text;
  886.     MyHUnlock(text);
  887.     MyHUnlock(theInfo);
  888.     
  889.     MySetHandleSize(text, len);
  890.     *info = theInfo;
  891.     *strings = text;
  892.     *numGroups = n;
  893.     SetLastTransactionTime(s);
  894.  
  895.     return nntpNoErr;
  896.     
  897. exit1:
  898.  
  899.     MyDisposeHandle(text);
  900.     MyDisposeHandle(theInfo);
  901.     (**s).netStream = nil;
  902.     return nntpOSErr;
  903.     
  904. exit2:
  905.     
  906.     MyDisposeHandle(text);
  907.     MyDisposeHandle(theInfo);
  908.     return nntpServerErr;
  909.     
  910. exit3:
  911.  
  912.     MyDisposeHandle(text);
  913.     MyDisposeHandle(theInfo);
  914.     return nntpOSErr;
  915. }
  916.  
  917.  
  918.  
  919. /*----------------------------------------------------------------------------
  920.     NntpGetGroupNames 
  921.     
  922.     Get the full list of group names or a list of names for new groups 
  923.     from the server.
  924.     
  925.     Entry:    stream = handle to stream.
  926.             time = 0 to get full group list.
  927.             time != 0 to only get new groups created since the specified time.
  928.             statusFilter = a C-format string of characters. Only the names of 
  929.                 groups with status not equal to one of the characters in this 
  930.                 string are returned. The string should be in lower case.
  931.     
  932.     Exit:    function result = result code.
  933.             strings = handle to group name strings.
  934.             numGroups = number of group names.
  935.             
  936.     The time parameter is in GetDateTime format (seconds since 01/01/04). It
  937.     is interpreted by the server, not by the Mac. To avoid missing new groups
  938.     due to differences between the server's time and the Mac's time, you
  939.     should subtract a healthy amount from the time of your last new groups 
  940.     check (e.g., 36 hours), then discard any duplicate groups returned.  
  941.             
  942.     The strings block contains the C-format group names. Group names are 
  943.     truncated to 127 characters.
  944. ----------------------------------------------------------------------------*/
  945.  
  946. NntpErr NntpGetGroupNames (NntpStreamHandle stream, unsigned long time,
  947.     char *statusFilter, Handle *strings, long *numGroups)
  948. {
  949.     TStreamHandle s;
  950.     NetStreamHandle netStream;
  951.     DateTimeRec timeRec;
  952.     Handle text = nil;
  953.     char *p, *pEnd, *q, *r, status;
  954.     long n, len;
  955.     NntpErr nntpErr;
  956.     
  957.     s = (TStreamHandle)stream;
  958.     nntpErr = ReOpenConnection(s);
  959.     if (nntpErr != nntpNoErr) return nntpErr;
  960.     netStream = (**s).netStream;
  961.     
  962.     if (time == 0) {
  963.         strcpy(gCommand, "LIST");
  964.     } else {
  965.         SecondsToDate(time, &timeRec);
  966.         timeRec.year = timeRec.year % 100;
  967.         sprintf(gCommand, "NEWGROUPS %02d%02d%02d %02d%02d%02d", 
  968.             timeRec.year, timeRec.month, timeRec.day,
  969.             timeRec.hour, timeRec.minute, timeRec.second);
  970.     }
  971.     
  972.     gErr = NetCommand(netStream, gCommand, &gResponseCode, gResponse);
  973.     if (gErr != noErr) goto exit1;
  974.     if (gResponseCode != (time == 0 ? 215 : 231)) goto exit2;
  975.     
  976.     gErr = NetGetText(netStream, &text);
  977.     if (gErr != noErr) goto exit1;
  978.  
  979.     MyHLock(text);
  980.     p = q = *text;
  981.     pEnd = p + MyGetHandleSize(text);
  982.     n = 0;
  983.     while (p < pEnd) {
  984.         gErr = NetGiveTime(false);
  985.         if (gErr != noErr) goto exit3;
  986.         r = p;
  987.         while (*r != ' ' && *r != CR) r++;
  988.         len = r-p;
  989.         if (len > 127) len = 127;
  990.         while (*r == ' ') r++;
  991.         while (*r != ' ' && *r != CR) r++;
  992.         while (*r == ' ') r++;
  993.         while (*r != ' ' && *r != CR) r++;
  994.         while (*r == ' ') r++;
  995.         status = tolower(*r);
  996.         while (*r != CR) r++;
  997.         if (strchr(statusFilter, status) == nil) {
  998.             *(p+len) = 0;
  999.             strcpy(q, p);
  1000.             q += len+1;
  1001.             n++;
  1002.         }
  1003.         p = r+1;
  1004.     }
  1005.     len = q-*text;
  1006.     MyHUnlock(text);
  1007.     
  1008.     MySetHandleSize(text, len);
  1009.     *strings = text;
  1010.     *numGroups = n;
  1011.     SetLastTransactionTime(s);
  1012.  
  1013.     return nntpNoErr;
  1014.     
  1015. exit1:
  1016.  
  1017.     MyDisposeHandle(text);
  1018.     (**s).netStream = nil;
  1019.     return nntpOSErr;
  1020.     
  1021. exit2:
  1022.     
  1023.     MyDisposeHandle(text);
  1024.     return nntpServerErr;
  1025.     
  1026. exit3:
  1027.  
  1028.     MyDisposeHandle(text);
  1029.     return nntpOSErr;
  1030. }
  1031.  
  1032.  
  1033.  
  1034. /*----------------------------------------------------------------------------
  1035.     NntpGetGroupInfo 
  1036.     
  1037.     Get info for a single group.
  1038.     
  1039.     Entry:    stream = handle to stream.
  1040.             group = C-format group name.
  1041.     
  1042.     Exit:    function result = result code.
  1043.             first = first article number in group.
  1044.             last = last article number in group.
  1045.             count = server's estimate of number of articles in the group.
  1046.             
  1047.     This function returns nntpNoSuchGroupErr if the group does not exist.
  1048.     
  1049.     If the newConnection stream option is set, the current connection to 
  1050.     the news server is closed and a new one is opened before getting the 
  1051.     group information. You need to do this with some kinds of news servers
  1052.     (e.g., the reference implementation server). This close/reopen is
  1053.     not done, however, if the previous call to NntpGetGroupInfo was 
  1054.     within 10 seconds ago. This prevents rapid sequences of calls to
  1055.     NntpGetGroupInfo from closing and reopening.
  1056. ----------------------------------------------------------------------------*/
  1057.  
  1058. NntpErr NntpGetGroupInfo (NntpStreamHandle stream, char *group, 
  1059.     long *first, long *last, long *count)
  1060. {
  1061.     TStreamHandle s;
  1062.     NetStreamHandle netStream;
  1063.     NntpErr nntpErr;
  1064.     char *p;
  1065.     static long prevTick = 0;
  1066.     
  1067.     s = (TStreamHandle)stream;
  1068.     
  1069.     if ((**s).options.newConnection && TickCount() > prevTick + 10*60) {
  1070.         netStream = (**s).netStream;
  1071.         if (netStream != nil) {
  1072.             NetClose(netStream);
  1073.             (**s).netStream = nil;
  1074.         }
  1075.     }
  1076.     
  1077.     nntpErr = ReOpenConnection(s);
  1078.     if (nntpErr != nntpNoErr) return nntpErr;
  1079.     netStream = (**s).netStream;
  1080.     
  1081.     *(**s).curGroup = 0;
  1082.     
  1083.     sprintf(gCommand, "GROUP %.127s", group);
  1084.     gErr = NetCommand(netStream, gCommand, &gResponseCode, gResponse);
  1085.     if (gErr != noErr) goto exit1;
  1086.     if (gResponseCode == 502) {
  1087.         *count = 0;
  1088.         *first = 1;
  1089.         *last = 0;
  1090.     } else if (gResponseCode == 211) {
  1091.         p = gResponse;
  1092.         CrackNum(&p);
  1093.         *count = CrackNum(&p);
  1094.         *first = CrackNum(&p);
  1095.         *last = CrackNum(&p);
  1096.     } else if (gResponseCode == 411) {
  1097.         return nntpNoSuchGroupErr;
  1098.     } else {
  1099.         return nntpServerErr;
  1100.     }
  1101.  
  1102.     SetCurrentGroup(s, group);
  1103.     SetLastTransactionTime(s);
  1104.     prevTick = TickCount();
  1105.     
  1106.     return nntpNoErr;
  1107.     
  1108. exit1:
  1109.  
  1110.     (**s).netStream = nil;
  1111.     return nntpOSErr;
  1112. }
  1113.  
  1114.  
  1115.  
  1116. /*----------------------------------------------------------------------------
  1117.     NntpGetMultipleGroupInfo 
  1118.     
  1119.     Get info for multiple groups.
  1120.     
  1121.     Entry:    stream = handle to stream.
  1122.             info = handle to array of group info.
  1123.             numGroups = number of groups in group info array.
  1124.             strings = handle to group name strings.
  1125.     
  1126.     Exit:    function result = result code.
  1127.             
  1128.     For each group in the array, the following information is returned in
  1129.     the group info array:
  1130.     
  1131.         first = first article number in group.
  1132.         last = last article number in group.
  1133.         count = estimate of number of articles in the group.
  1134.         ok = true if other info is valid, false if group does not exist
  1135.             or some other server error occured.
  1136.     
  1137.     If the newConnection stream option is set, the current connection to 
  1138.     the news server is closed and a new one is opened before getting the 
  1139.     group information. You need to do this with some kinds of news servers
  1140.     (e.g., the reference implementation server).
  1141.     
  1142.     If the batchedCmds stream option is set, multiple GROUP commands are 
  1143.     sent to the news server in a batch, and the responses are processed as
  1144.     they arrive. If the batchedCmds stream option is not set, the GROUP 
  1145.     commands are sent one at a time. Batched commands are much faster, but
  1146.     they do not work with all servers.
  1147. ----------------------------------------------------------------------------*/
  1148.  
  1149. NntpErr NntpGetMultipleGroupInfo (NntpStreamHandle stream, NntpGroupInfoHandle info,
  1150.     long numGroups, Handle strings)
  1151. {
  1152.     TStreamHandle s;
  1153.     NetStreamHandle netStream;
  1154.     NntpErr nntpErr;
  1155.     NntpGroupInfoPtr x, xEnd;
  1156.     Boolean savedNewConnectionOption;
  1157.     long cmdBufSize;
  1158.     Handle cmdBuf;
  1159.     char *p;
  1160.     short len;
  1161.     short state1, state2;
  1162.     
  1163.     state1 = MyHGetState(info);
  1164.     state2 = MyHGetState(strings);
  1165.     
  1166.     if (numGroups == 0) return nntpNoErr;
  1167.     
  1168.     s = (TStreamHandle)stream;
  1169.     
  1170.     if ((**s).options.newConnection) {
  1171.         netStream = (**s).netStream;
  1172.         if (netStream != nil) {
  1173.             NetClose(netStream);
  1174.             (**s).netStream = nil;
  1175.         }
  1176.     }
  1177.     
  1178.     nntpErr = ReOpenConnection(s);
  1179.     if (nntpErr != nntpNoErr) return nntpErr;
  1180.     netStream = (**s).netStream;
  1181.     
  1182.     *(**s).curGroup = 0;
  1183.     
  1184.     if ((**s).options.batchedCmds) {
  1185.     
  1186.         cmdBufSize = 0;
  1187.         xEnd = *info + numGroups;
  1188.         for (x = *info; x < xEnd; x++) 
  1189.             cmdBufSize += 7 + strlen(*strings + x->offset);
  1190.         gErr = MyNewHandle(cmdBufSize, &cmdBuf);
  1191.         if (gErr != noErr) return nntpOSErr;
  1192.         p = *cmdBuf;
  1193.         xEnd = *info + numGroups;
  1194.         for (x = *info; x < xEnd; x++) {
  1195.             BlockMoveData("GROUP ", p, 6);
  1196.             p += 6;
  1197.             len = strlen(*strings + x->offset);
  1198.             BlockMoveData(*strings + x->offset, p, len);
  1199.             p += len;
  1200.             *p++ = CR;
  1201.         }
  1202.         gInfo = info;
  1203.         gInfoIndex = 0;
  1204.         gGroupInfoServerError = false;
  1205.         gErr = NetBatchedCommands(netStream, cmdBuf, DoOneGroupCmdResponse);
  1206.         MyDisposeHandle(cmdBuf);
  1207.         if (gErr != noErr) goto exit1;
  1208.         if (gGroupInfoServerError) return nntpServerErr;
  1209.     
  1210.     } else {
  1211.     
  1212.         savedNewConnectionOption = (**s).options.newConnection;
  1213.         (**s).options.newConnection = false;
  1214.         MyHLock(info);
  1215.         MyHLock(strings);
  1216.         xEnd = *info + numGroups;
  1217.         for (x = *info; x < xEnd; x++) {
  1218.             nntpErr = NntpGetGroupInfo(stream, *strings + x->offset,
  1219.                 &x->first, &x->last, &x->count);
  1220.             if (nntpErr == nntpNoErr) {
  1221.                 x->ok = true;
  1222.             } else if (nntpErr == nntpNoSuchGroupErr || nntpErr == nntpServerErr) {
  1223.                 x->ok = false;
  1224.             } else {
  1225.                 goto exit2;
  1226.             }
  1227.         }
  1228.         MyHSetState(info, state1);
  1229.         MyHSetState(strings, state2);
  1230.         (**s).options.newConnection = savedNewConnectionOption;
  1231.     
  1232.     }
  1233.  
  1234.     SetLastTransactionTime(s);
  1235.     
  1236.     return nntpNoErr;
  1237.     
  1238. exit1:
  1239.  
  1240.     MyHSetState(info, state1);
  1241.     MyHSetState(strings, state2);
  1242.     (**s).netStream = nil;
  1243.     return nntpOSErr;
  1244.  
  1245. exit2:
  1246.  
  1247.     MyHSetState(info, state1);
  1248.     MyHSetState(strings, state2);
  1249.     (**s).options.newConnection = savedNewConnectionOption;
  1250.     return nntpErr;
  1251.  
  1252. }
  1253.  
  1254.  
  1255.  
  1256. /*----------------------------------------------------------------------------
  1257.     NntpGetHeaders 
  1258.     
  1259.     Get selected article headers.
  1260.     
  1261.     Entry:    stream = handle to stream.
  1262.             group = group name.
  1263.             first = first article number.
  1264.             last = last article number.
  1265.             header = header name.
  1266.             pattern = search string.
  1267.             buildXPAT = pointer to function to build XPAT pattern
  1268.                 for search string. Ignored if pattern nil or empty.
  1269.             matchPattern = pointer to function to compare search string
  1270.                 to target string. Ignored if pattern nil or empty.
  1271.             
  1272.     Exit:    function result = result code.
  1273.             info = handle to array of header info.
  1274.             strings = handle to header strings.
  1275.             numHeaders = number of headers.
  1276.     
  1277.     If the pattern parameter is nil or the empty string, all headers in the 
  1278.     range [first,last] are returned. 
  1279.     
  1280.     If the pattern parameter is not nil or empty, only headers in the range
  1281.     [first,last] which match the pattern are returned.
  1282.             
  1283.     The strings block contains the C-format header strings, one after another.
  1284.     The offset fields in the header info array contain offsets into this
  1285.     block. Header strings are truncated to 255 characters, and have leading
  1286.     and trailing blanks stripped.
  1287.     
  1288.     All returned elements of the header info array have article numbers in 
  1289.     the range [first,last]. The header info array is sorted by article number, 
  1290.     with any duplicates eliminated.
  1291.     
  1292.     If the useXPAT stream option is set, and if a search pattern is specified,
  1293.     the buildXPAT callback function is called to build the regular expression
  1294.     XPAT pattern. An XPAT command is then sent to the server to get the 
  1295.     matching headers.
  1296.     
  1297.     If the useXPAT stream option is not set, or if the server does not support
  1298.     the XPAT command, all the headers are fetched from the server, and the
  1299.     matchPattern callback function is called to extract the matching headers.
  1300. ----------------------------------------------------------------------------*/
  1301.  
  1302. NntpErr NntpGetHeaders (NntpStreamHandle stream, char *group, long first, long last,
  1303.     char *header, char *pattern,
  1304.     void (*buildXPAT)(char *pattern, char *regExp, short regExpLen),
  1305.     Boolean (*matchPattern)(char *pattern, char *string),
  1306.     NntpHeaderInfoHandle *info, Handle *strings, long *numHeaders)
  1307. {
  1308.     TStreamHandle s;
  1309.     NetStreamHandle netStream;
  1310.     NntpErr nntpErr;
  1311.     Handle text = nil;
  1312.     NntpHeaderInfoHandle theInfo = nil;
  1313.     NntpHeaderInfoPtr x;
  1314.     char *p, *pEnd, *q, *r, *t;
  1315.     long n, len, number, prevNumber;
  1316.     char xpatCmd[1000];
  1317.     Boolean mustFilter;
  1318.     Boolean mustSort = false;
  1319.     long numToMove;
  1320.     
  1321.     s = (TStreamHandle)stream;
  1322.     nntpErr = ReOpenConnection(s);
  1323.     if (nntpErr != nntpNoErr) return nntpErr;
  1324.     netStream = (**s).netStream;
  1325.     
  1326.     if (!MyStrEqual(group, (**s).curGroup)) {
  1327.         *(**s).curGroup = 0;
  1328.         sprintf(gCommand, "GROUP %.127s", group);
  1329.         gErr = NetCommand(netStream, gCommand, &gResponseCode, gResponse);
  1330.         if (gErr != noErr) goto exit1;
  1331.         if (gResponseCode == 411) return nntpNoSuchGroupErr;
  1332.         if (gResponseCode != 211) return nntpServerErr;
  1333.         SetCurrentGroup(s, group);
  1334.     }
  1335.         
  1336.     while (true) {
  1337.         if (pattern == nil || *pattern == 0 || !(**s).options.useXPAT || !(**s).serverHasXPAT) {
  1338.             sprintf(gCommand, "XHDR %.127s %ld-%ld", header, first, last);
  1339.             gErr = NetCommand(netStream, gCommand, &gResponseCode, gResponse);
  1340.             if (gErr != noErr) goto exit1;
  1341.             if (gResponseCode != 221) return nntpServerErr;
  1342.             mustFilter = pattern != nil && *pattern != 0;
  1343.             break;
  1344.         } else {
  1345.             sprintf(xpatCmd, "XPAT %.127s %ld-%ld ", header, first, last);
  1346.             len = strlen(xpatCmd);
  1347.             (*buildXPAT)(pattern, xpatCmd + len, 1000 - len);
  1348.             len = strlen(xpatCmd);
  1349.             if (len > 255) len = 255;
  1350.             BlockMoveData(xpatCmd, gCommand, len);
  1351.             *(gCommand + len) = 0;
  1352.             gErr = NetCommand(netStream, xpatCmd, &gResponseCode, gResponse);
  1353.             if (gErr != noErr) goto exit1;
  1354.             if (gResponseCode == 500) {
  1355.                 (**s).serverHasXPAT = false;
  1356.                 continue;
  1357.             }
  1358.             if (gResponseCode != 221) return nntpServerErr;
  1359.             mustFilter = false;
  1360.             break;
  1361.         }
  1362.     }
  1363.     
  1364.     gErr = NetGetText(netStream, &text);
  1365.     if (gErr != noErr) goto exit1;
  1366.  
  1367.     pEnd = *text + MyGetHandleSize(text);
  1368.     n = 0;
  1369.     for (p = *text; p < pEnd; p++) if (*p == CR) n++;
  1370.     
  1371.     gErr = MyNewHandle(n*sizeof(NntpHeaderInfo), &theInfo);
  1372.     if (gErr != noErr) goto exit3;
  1373.  
  1374.     MyHLock(text);
  1375.     MyHLock(theInfo);
  1376.     x = *theInfo;
  1377.     p = q = *text;
  1378.     pEnd = *text + MyGetHandleSize(text);
  1379.     n = 0;
  1380.     while (p < pEnd) {
  1381.         number = CrackNum(&p);
  1382.         if (number < first || number > last) {
  1383.             while (*p != CR) p++;
  1384.             p++;
  1385.             continue;
  1386.         }
  1387.         while (*p == ' ') p++;
  1388.         r = p;
  1389.         while (*r != CR) r++;
  1390.         t = r-1;
  1391.         while (t >= p && *t == ' ') t--;
  1392.         len = t-p+1;
  1393.         if (len > 255) len = 255;
  1394.         *(p+len) = 0;
  1395.         if (!mustFilter || (*matchPattern)(pattern, p)) {
  1396.             if (n > 0 && number <= prevNumber) mustSort = true;
  1397.             prevNumber = number;
  1398.             x->number = number;
  1399.             x->offset = q - *text;
  1400.             strcpy(q, p);
  1401.             q += len+1;
  1402.             n++;
  1403.             x++;
  1404.         }
  1405.         p = r+1;
  1406.     }
  1407.     if (mustSort) {
  1408.         gErr = FastQSort(*theInfo, n, sizeof(NntpHeaderInfo), 
  1409.             (short(*)(void*,void*))SortHeadersCompare);
  1410.         if (gErr != noErr) goto exit3;
  1411.         if (n > 1) {
  1412.             x =  *theInfo;
  1413.             for (numToMove = (n-1)*sizeof(NntpHeaderInfo); 
  1414.                 numToMove > 0; 
  1415.                 numToMove -= sizeof(NntpHeaderInfo))
  1416.             {
  1417.                 if (x->number == (x+1)->number) {
  1418.                     BlockMoveData(x+1, x, numToMove);
  1419.                     n--;
  1420.                 } else {
  1421.                     x++;
  1422.                 }
  1423.             }
  1424.         }
  1425.     }
  1426.     len = q - *text;
  1427.     MyHUnlock(text);
  1428.     MyHUnlock(theInfo);
  1429.     
  1430.     MySetHandleSize(text, len);
  1431.     MySetHandleSize(theInfo, n*sizeof(NntpHeaderInfo));
  1432.     *info = theInfo;
  1433.     *strings = text;
  1434.     *numHeaders = n;
  1435.     SetLastTransactionTime(s);
  1436.     
  1437.     return nntpNoErr;
  1438.     
  1439. exit1:
  1440.  
  1441.     MyDisposeHandle(text);
  1442.     MyDisposeHandle(theInfo);
  1443.     (**s).netStream = nil;
  1444.     return nntpOSErr;
  1445.     
  1446. exit2:
  1447.     
  1448.     MyDisposeHandle(text);
  1449.     MyDisposeHandle(theInfo);
  1450.     return nntpServerErr;
  1451.     
  1452. exit3:
  1453.  
  1454.     MyDisposeHandle(text);
  1455.     MyDisposeHandle(theInfo);
  1456.     return nntpOSErr;
  1457. }
  1458.  
  1459.  
  1460.  
  1461. /*----------------------------------------------------------------------------
  1462.     NntpGetArticle 
  1463.     
  1464.     Get an article.
  1465.     
  1466.     Entry:    stream = handle to stream.
  1467.             group = group name, or nil if fetching by message id.
  1468.             number = article number. Ignored if fetching by message id.
  1469.             id = message id string, including < and > delimiters. Ignored
  1470.                 if fetching by article number.
  1471.             part = which part of the article to get:
  1472.                 "ARTICLE": full article text, header and body.
  1473.                 "HEAD": only article header.
  1474.                 "BODY": only article body.
  1475.             truncateIfAttachedFile = true to truncate the article if it
  1476.                 contains an attached file. The BinHex or uuencode text
  1477.                 for the attached file is not returned.
  1478.             requireEncodedTextBeginLIne = true if BinHex or uuencode text
  1479.                 must include special "begin" flag line.
  1480.             
  1481.     Exit:    function result = result code.
  1482.             text = handle to article text, including header lines, with CR 
  1483.                 line terminators.
  1484.             *attachedFile = true if article contains an attached file which
  1485.                 was truncated.
  1486. ----------------------------------------------------------------------------*/
  1487.  
  1488. NntpErr NntpGetArticle (NntpStreamHandle stream, char *group, long number, 
  1489.     char *id, char *part, Boolean truncateIfAttachedFile, 
  1490.     Boolean requireEncodedTextBeginLine, Handle *text,
  1491.     Boolean *attachedFile)
  1492. {
  1493.     TStreamHandle s;
  1494.     NetStreamHandle netStream;
  1495.     NntpErr nntpErr;
  1496.     
  1497.     s = (TStreamHandle)stream;
  1498.     nntpErr = ReOpenConnection(s);
  1499.     if (nntpErr != nntpNoErr) return nntpErr;
  1500.     netStream = (**s).netStream;
  1501.     
  1502.     gRequireEncodedTextBeginLine = requireEncodedTextBeginLine;
  1503.     
  1504.     if (group != nil && !MyStrEqual(group, (**s).curGroup)) {
  1505.         *(**s).curGroup = 0;
  1506.         sprintf(gCommand, "GROUP %.127s", group);
  1507.         gErr = NetCommand(netStream, gCommand, &gResponseCode, gResponse);
  1508.         if (gErr != noErr) goto exit;
  1509.         if (gResponseCode == 411) return nntpNoSuchGroupErr;
  1510.         if (gResponseCode != 211) return nntpServerErr;
  1511.         SetCurrentGroup(s, group);
  1512.     }
  1513.  
  1514.     if (group != nil) {
  1515.         sprintf(gCommand, "%s %ld", part, number);
  1516.     } else {
  1517.         sprintf(gCommand, "%s %.127s", part, id);
  1518.     }
  1519.     gErr = NetCommand(netStream, gCommand, &gResponseCode, gResponse);
  1520.     if (gErr != noErr) goto exit;
  1521.     if (gResponseCode == 423 || gResponseCode == 430) return nntpNoSuchArticleErr;
  1522.     if (gResponseCode != 220 && gResponseCode != 221 &&
  1523.         gResponseCode != 222) return nntpServerErr;
  1524.     
  1525.     if (truncateIfAttachedFile) {
  1526.         gErr = NetGetTextWithTruncation(netStream, CheckForAttachedFile, text, attachedFile);
  1527.         if (gErr != noErr) goto exit;
  1528.         if (*attachedFile) (**s).netStream = nil;
  1529.     } else {
  1530.         gErr = NetGetText(netStream, text);
  1531.         if (gErr != noErr) goto exit;
  1532.     }
  1533.  
  1534.     SetLastTransactionTime(s);
  1535.     
  1536.     return nntpNoErr;
  1537.     
  1538. exit:
  1539.  
  1540.     (**s).netStream = nil;
  1541.     return nntpOSErr;
  1542. }
  1543.  
  1544.  
  1545.  
  1546. /*----------------------------------------------------------------------------
  1547.     NntpGetChunkyArticle
  1548.     
  1549.     Get an article in chunks.
  1550.     
  1551.     Entry:    stream = handle to stream.
  1552.             group = group name, or nil if fetching by message id.
  1553.             number = article number. Ignored if fetching by message id.
  1554.             id = message id string, including < and > delimiters. Ignored
  1555.                 if fetching by article number.
  1556.             part = which part of the article to get:
  1557.                 "ARTICLE": full article text, header and body.
  1558.                 "HEAD": only article header.
  1559.                 "BODY": only article body.
  1560.             chunkFunction = pointer to chunk processing function
  1561.     
  1562.     Exit:    function result = error code.
  1563.             *aborted = true if operation was aborted.
  1564.     
  1565.     The chunk processing function is called as the article text is received 
  1566.     from the server. It must be declared as follows:
  1567.     
  1568.     OSErr ChunkFunction (Ptr t, long tLen)
  1569.     
  1570.     Entry:    t = pointer to chunk.
  1571.             tLen = length of chunk.
  1572.             
  1573.     Exit:    function result = error code.
  1574.             *abort = true to abort the operation.
  1575.     
  1576.     The text passed to the chunk function is raw. It contains CRLF line 
  1577.     terminators and doubled leading ".." characters. The chunks do not
  1578.     necessarily end on CRLF line boundaries. The terminating "." character
  1579.     on a line by itself is *not* passed to the chunk function, however.
  1580. ----------------------------------------------------------------------------*/
  1581.  
  1582. NntpErr NntpGetChunkyArticle (NntpStreamHandle stream, char *group, long number, 
  1583.     char *id, char *part, NntpChunkFunction chunkFunction,
  1584.     Boolean *aborted)
  1585. {
  1586.     TStreamHandle s;
  1587.     NetStreamHandle netStream;
  1588.     NntpErr nntpErr;
  1589.     
  1590.     s = (TStreamHandle)stream;
  1591.     nntpErr = ReOpenConnection(s);
  1592.     if (nntpErr != nntpNoErr) return nntpErr;
  1593.     netStream = (**s).netStream;
  1594.     
  1595.     if (group != nil && !MyStrEqual(group, (**s).curGroup)) {
  1596.         *(**s).curGroup = 0;
  1597.         sprintf(gCommand, "GROUP %.127s", group);
  1598.         gErr = NetCommand(netStream, gCommand, &gResponseCode, gResponse);
  1599.         if (gErr != noErr) goto exit;
  1600.         if (gResponseCode == 411) return nntpNoSuchGroupErr;
  1601.         if (gResponseCode != 211) return nntpServerErr;
  1602.         SetCurrentGroup(s, group);
  1603.     }
  1604.  
  1605.     if (group != nil) {
  1606.         sprintf(gCommand, "%s %ld", part, number);
  1607.     } else {
  1608.         sprintf(gCommand, "%s %.127s", part, id);
  1609.     }
  1610.     gErr = NetCommand(netStream, gCommand, &gResponseCode, gResponse);
  1611.     if (gErr != noErr) goto exit;
  1612.     if (gResponseCode == 423 || gResponseCode == 430) return nntpNoSuchArticleErr;
  1613.     if (gResponseCode != 220 && gResponseCode != 221 &&
  1614.         gResponseCode != 222) return nntpServerErr;
  1615.     
  1616.     gErr = NetGetChunkyText(netStream, chunkFunction, aborted);
  1617.     if (gErr != noErr) goto exit;
  1618.     
  1619.     if (*aborted) (**s).netStream = nil;
  1620.  
  1621.     SetLastTransactionTime(s);
  1622.     
  1623.     return nntpNoErr;
  1624.     
  1625. exit:
  1626.  
  1627.     (**s).netStream = nil;
  1628.     return nntpOSErr;
  1629. }
  1630.  
  1631.  
  1632.  
  1633. /*----------------------------------------------------------------------------
  1634.     NntpPostArticle 
  1635.     
  1636.     Post an article.
  1637.     
  1638.     Entry:    stream = handle to stream.
  1639.             text = handle to article text, including header lines, 
  1640.                 with CR line terminators.
  1641.                 Warning: the memory block is modified by the function.
  1642.                 The memory block must be unlocked and nonpurgeable.
  1643.             
  1644.     Exit:    function result = result code.
  1645.             postIndeterminate = true if entire article sent, but error occured
  1646.                 or user canceled before final server response received. The
  1647.                 article may or may not have been posted successfully.
  1648. ----------------------------------------------------------------------------*/
  1649.  
  1650. NntpErr NntpPostArticle (NntpStreamHandle stream, Handle text, Boolean *postIndeterminate)
  1651. {
  1652.     TStreamHandle s;
  1653.     NetStreamHandle netStream;
  1654.     NntpErr nntpErr;
  1655.     
  1656.     *postIndeterminate = false;
  1657.     
  1658.     s = (TStreamHandle)stream;
  1659.     nntpErr = ReOpenConnection(s);
  1660.     if (nntpErr != nntpNoErr) return nntpErr;
  1661.     netStream = (**s).netStream;
  1662.     
  1663.     strcpy(gCommand, "POST");
  1664.     gErr = NetCommand(netStream, gCommand, &gResponseCode, gResponse);
  1665.     if (gErr != noErr) goto exit1;
  1666.     if (gResponseCode != 340) return nntpServerErr;
  1667.     
  1668.     gErr = NetSendText(netStream, text);
  1669.     if (gErr != noErr) goto exit1;
  1670.     
  1671.     *postIndeterminate = true;
  1672.     
  1673.     gErr = NetGetExtraResponse(netStream, &gResponseCode, gResponse);
  1674.     if (gErr != noErr) goto exit1;
  1675.     *postIndeterminate = false;
  1676.     if (gResponseCode != 240) return nntpServerErr;
  1677.  
  1678.     SetLastTransactionTime(s);
  1679.     
  1680.     return nntpNoErr;
  1681.     
  1682. exit1:
  1683.  
  1684.     (**s).netStream = nil;
  1685.     return nntpOSErr;
  1686. }
  1687.  
  1688.  
  1689.  
  1690. /*----------------------------------------------------------------------------
  1691.     NntpAuthorize 
  1692.     
  1693.     Authorize user.
  1694.     
  1695.     Entry:    stream = handle to stream.
  1696.             username = username.
  1697.             password = password.
  1698.             
  1699.     Exit:    function result = result code.
  1700. ----------------------------------------------------------------------------*/
  1701.  
  1702. NntpErr NntpAuthorize (NntpStreamHandle stream, char *username, char *password)
  1703. {
  1704.     TStreamHandle s;
  1705.     NetStreamHandle netStream;
  1706.     NntpErr nntpErr;
  1707.     
  1708.     s = (TStreamHandle)stream;
  1709.     nntpErr = ReOpenConnection(s);
  1710.     if (nntpErr != nntpNoErr) return nntpErr;
  1711.     netStream = (**s).netStream;
  1712.  
  1713.     nntpErr = Authorize(netStream, username, password);
  1714.     if (nntpErr != nntpNoErr) goto exit;
  1715.     
  1716.     SetLastTransactionTime(s);
  1717.     
  1718.     return nntpNoErr;
  1719.     
  1720. exit:
  1721.  
  1722.     (**s).netStream = nil;
  1723.     return nntpErr;
  1724. }
  1725.  
  1726.  
  1727.  
  1728. /*----------------------------------------------------------------------------
  1729.     NntpGetHello 
  1730.     
  1731.     Get the server hello message.
  1732.     
  1733.     Entry:    stream = handle to stream.
  1734.             
  1735.     Exit:    function result = result code (always nntpNoErr).
  1736.             msg = hello message.
  1737. ----------------------------------------------------------------------------*/
  1738.  
  1739. NntpErr NntpGetHello (NntpStreamHandle stream, CStr255 msg)
  1740. {
  1741.     TStreamHandle s;
  1742.     
  1743.     s = (TStreamHandle)stream;
  1744.     strcpy(msg, (**s).helloMsg);
  1745.     return nntpNoErr;
  1746. }
  1747.  
  1748.  
  1749.  
  1750. /*----------------------------------------------------------------------------
  1751.     NntpGetHelp 
  1752.     
  1753.     Get the server help text.
  1754.     
  1755.     Entry:    stream = handle to stream.
  1756.             
  1757.     Exit:    function result = result code.
  1758.             text = handle to help text, with CR-terminated lines.
  1759. ----------------------------------------------------------------------------*/
  1760.  
  1761. NntpErr NntpGetHelp (NntpStreamHandle stream, Handle *text)
  1762. {
  1763.     TStreamHandle s;
  1764.     NetStreamHandle netStream;
  1765.     NntpErr nntpErr;
  1766.     
  1767.     s = (TStreamHandle)stream;
  1768.     nntpErr = ReOpenConnection(s);
  1769.     if (nntpErr != nntpNoErr) return nntpErr;
  1770.     netStream = (**s).netStream;
  1771.     
  1772.     strcpy(gCommand, "HELP");
  1773.     gErr = NetCommand(netStream, gCommand, &gResponseCode, gResponse);
  1774.     if (gErr != noErr) goto exit1;
  1775.     if (gResponseCode != 100) return nntpServerErr;
  1776.     
  1777.     gErr = NetGetText(netStream, text);
  1778.     if (gErr != noErr) goto exit1;
  1779.  
  1780.     SetLastTransactionTime(s);
  1781.     
  1782.     return nntpNoErr;
  1783.     
  1784. exit1:
  1785.  
  1786.     (**s).netStream = nil;
  1787.     return nntpOSErr;
  1788. }
  1789.  
  1790.  
  1791.  
  1792. /*----------------------------------------------------------------------------
  1793.     NntpGetIPAddr 
  1794.     
  1795.     Get server IP address.
  1796.         
  1797.     Entry:    stream = handle to stream.
  1798.             
  1799.     Exit:    function result = result code (always nntpNoErr).
  1800.             ipAddr = IP address of news server.
  1801. ----------------------------------------------------------------------------*/
  1802.  
  1803. NntpErr NntpGetIPAddr (NntpStreamHandle stream, unsigned long *ipAddr)
  1804. {
  1805.     TStreamHandle s;
  1806.     
  1807.     s = (TStreamHandle)stream;
  1808.     *ipAddr = (**s).addr;
  1809.     return nntpNoErr;
  1810. }
  1811.  
  1812.  
  1813.  
  1814. /*----------------------------------------------------------------------------
  1815.     NntpGetServerErrInfo 
  1816.     
  1817.     Get server error information.
  1818.     
  1819.     Exit:    cmd = C-format server command.
  1820.             num = server error number.
  1821.             msg = C-format server error message.
  1822. ----------------------------------------------------------------------------*/
  1823.  
  1824. void NntpGetServerErrInfo (CStr255 cmd, long *num, CStr255 msg)
  1825. {
  1826.     strcpy(cmd, gCommand);
  1827.     *num = gResponseCode;
  1828.     strcpy(msg, gResponse);
  1829. }
  1830.  
  1831.  
  1832.  
  1833. /*----------------------------------------------------------------------------
  1834.     NntpGetOSErr 
  1835.     
  1836.     Get the OS error code.
  1837.     
  1838.     Exit:    function result = OS error code.
  1839. ----------------------------------------------------------------------------*/
  1840.  
  1841. OSErr NntpGetOSErr (void)
  1842. {
  1843.     return gErr;
  1844. }